Merge "Update deprecated MutableTextureState Vulkan constructor call" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index 0e413c4..1091706 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -25478,34 +25478,34 @@
     field public short preset;
   }
 
-  public class Virtualizer extends android.media.audiofx.AudioEffect {
-    ctor public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
-    method public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public boolean getStrengthSupported();
-    method public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
-    method public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    method public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
-    field public static final int PARAM_STRENGTH = 1; // 0x1
-    field public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
-    field public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
-    field public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
-    field public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
-    field public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
+  @Deprecated public class Virtualizer extends android.media.audiofx.AudioEffect {
+    ctor @Deprecated public Virtualizer(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.RuntimeException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean canVirtualize(int, int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean forceVirtualizationMode(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public android.media.audiofx.Virtualizer.Settings getProperties() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public short getRoundedStrength() throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean getSpeakerAngles(int, int, int[]) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public boolean getStrengthSupported();
+    method @Deprecated public int getVirtualizationMode() throws java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public void setParameterListener(android.media.audiofx.Virtualizer.OnParameterChangeListener);
+    method @Deprecated public void setProperties(android.media.audiofx.Virtualizer.Settings) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    method @Deprecated public void setStrength(short) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException, java.lang.UnsupportedOperationException;
+    field @Deprecated public static final int PARAM_STRENGTH = 1; // 0x1
+    field @Deprecated public static final int PARAM_STRENGTH_SUPPORTED = 0; // 0x0
+    field @Deprecated public static final int VIRTUALIZATION_MODE_AUTO = 1; // 0x1
+    field @Deprecated public static final int VIRTUALIZATION_MODE_BINAURAL = 2; // 0x2
+    field @Deprecated public static final int VIRTUALIZATION_MODE_OFF = 0; // 0x0
+    field @Deprecated public static final int VIRTUALIZATION_MODE_TRANSAURAL = 3; // 0x3
   }
 
-  public static interface Virtualizer.OnParameterChangeListener {
-    method public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
+  @Deprecated public static interface Virtualizer.OnParameterChangeListener {
+    method @Deprecated public void onParameterChange(android.media.audiofx.Virtualizer, int, int, short);
   }
 
-  public static class Virtualizer.Settings {
-    ctor public Virtualizer.Settings();
-    ctor public Virtualizer.Settings(String);
-    field public short strength;
+  @Deprecated public static class Virtualizer.Settings {
+    ctor @Deprecated public Virtualizer.Settings();
+    ctor @Deprecated public Virtualizer.Settings(String);
+    field @Deprecated public short strength;
   }
 
   public class Visualizer {
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 39b6aeb..8c70501 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -86,3 +86,11 @@
     description: "This flag is used to enabled the Wallet Role for all users on the device"
     bug: "283989236"
 }
+
+flag {
+  name: "signature_permission_allowlist_enabled"
+  is_fixed_read_only: true
+  namespace: "permissions"
+  description: "Enable signature permission allowlist"
+  bug: "308573169"
+}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e0bda91..3c36227 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -26,7 +26,6 @@
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
@@ -1012,10 +1011,8 @@
     // Used to check if there were any view invalidations in
     // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
     private boolean mHasInvalidation = false;
-    // Used to check if it is in the frame rate boosting period.
+    // Used to check if it is in the touch boosting period.
     private boolean mIsFrameRateBoosting = false;
-    // Used to check if it is in touch boosting period.
-    private boolean mIsTouchBoosting = false;
     // Used to check if there is a message in the message queue
     // for idleness handling.
     private boolean mHasIdledMessage = false;
@@ -6424,12 +6421,11 @@
                      * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
                      */
                     mIsFrameRateBoosting = false;
-                    mIsTouchBoosting = false;
                     setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
                             mLastPreferredFrameRateCategory));
                     break;
                 case MSG_CHECK_INVALIDATION_IDLE:
-                    if (!mHasInvalidation && !mIsFrameRateBoosting && !mIsTouchBoosting) {
+                    if (!mHasInvalidation && !mIsFrameRateBoosting) {
                         mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
                         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
                         mHasIdledMessage = false;
@@ -7452,7 +7448,7 @@
             // For the variable refresh rate project
             if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
                 // set the frame rate to the maximum value.
-                mIsTouchBoosting = true;
+                mIsFrameRateBoosting = true;
                 setPreferredFrameRateCategory(mPreferredFrameRateCategory);
             }
             /**
@@ -12204,16 +12200,8 @@
             return;
         }
 
-        int frameRateCategory = mIsTouchBoosting
-                ? FRAME_RATE_CATEGORY_HIGH_HINT : preferredFrameRateCategory;
-
-        // FRAME_RATE_CATEGORY_HIGH has a higher precedence than FRAME_RATE_CATEGORY_HIGH_HINT
-        // For now, FRAME_RATE_CATEGORY_HIGH_HINT is used for boosting with user interaction.
-        // FRAME_RATE_CATEGORY_HIGH is for boosting without user interaction
-        // (e.g., Window Initialization).
-        if (mIsFrameRateBoosting || mInsetsAnimationRunning) {
-            frameRateCategory = FRAME_RATE_CATEGORY_HIGH;
-        }
+        int frameRateCategory = mIsFrameRateBoosting || mInsetsAnimationRunning
+                ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
 
         try {
             if (mLastPreferredFrameRateCategory != frameRateCategory) {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index cfbda84..cf3eb12 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -19,7 +19,6 @@
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
-import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
 import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
@@ -576,13 +575,8 @@
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_HIGH_HINT);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
-            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH_HINT);
-            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
             assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
             viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
diff --git a/libs/hwui/jni/PathMeasure.cpp b/libs/hwui/jni/PathMeasure.cpp
index acf893e..79acb6c 100644
--- a/libs/hwui/jni/PathMeasure.cpp
+++ b/libs/hwui/jni/PathMeasure.cpp
@@ -17,7 +17,11 @@
 
 #include "GraphicsJNI.h"
 
+#include "SkMatrix.h"
+#include "SkPath.h"
 #include "SkPathMeasure.h"
+#include "SkPoint.h"
+#include "SkScalar.h"
 
 /*  We declare an explicit pair, so that we don't have to rely on the java
     client to be sure not to edit the path while we have an active measure
diff --git a/media/java/android/media/audiofx/Virtualizer.java b/media/java/android/media/audiofx/Virtualizer.java
index 74b6fc1..71147f4 100644
--- a/media/java/android/media/audiofx/Virtualizer.java
+++ b/media/java/android/media/audiofx/Virtualizer.java
@@ -46,6 +46,11 @@
  * <p>See {@link android.media.MediaPlayer#getAudioSessionId()} for details on audio sessions.
  * <p>See {@link android.media.audiofx.AudioEffect} class for more details on controlling
  * audio effects.
+ *
+ * @deprecated use the {@link android.media.Spatializer} class to query the capabilities of the
+ *     platform with regards to spatialization, a different name for audio channel virtualization,
+ *     and the {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)} to
+ *     characterize how you want your content to be played when spatialization is supported.
  */
 
 public class Virtualizer extends AudioEffect {
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 3236130..2c35c77 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -89,6 +89,13 @@
 }
 
 flag {
+    name: "notification_avalanche_suppression"
+    namespace: "systemui"
+    description: "After notification avalanche floodgate event, suppress HUNs completely."
+    bug: "321089634"
+}
+
+flag {
     name: "notification_background_tint_optimization"
     namespace: "systemui"
     description: "Re-enable the codepath that removed tinting of notifications when the"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 6f62afc..dc8b97a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -139,6 +139,18 @@
         }
 
     @Test
+    fun topClippingBounds() =
+        testScope.runTest {
+            assertThat(underTest.topClippingBounds.value).isNull()
+
+            underTest.topClippingBounds.value = 50
+            assertThat(underTest.topClippingBounds.value).isEqualTo(50)
+
+            underTest.topClippingBounds.value = 500
+            assertThat(underTest.topClippingBounds.value).isEqualTo(500)
+        }
+
+    @Test
     fun clockPosition() =
         testScope.runTest {
             assertThat(underTest.clockPosition.value).isEqualTo(Position(0, 0))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 1eaa060..cceb767 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
 import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -54,13 +55,12 @@
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
     private val screenOffAnimationController = kosmos.screenOffAnimationController
     private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
     private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
     private val dozeParameters = kosmos.dozeParameters
-    private val underTest by lazy {
-        kosmos.keyguardRootViewModel
-    }
+    private val underTest by lazy { kosmos.keyguardRootViewModel }
 
     @Before
     fun setUp() {
@@ -207,6 +207,19 @@
         }
 
     @Test
+    fun topClippingBounds() =
+        testScope.runTest {
+            val topClippingBounds by collectLastValue(underTest.topClippingBounds)
+            assertThat(topClippingBounds).isNull()
+
+            keyguardRepository.topClippingBounds.value = 50
+            assertThat(topClippingBounds).isEqualTo(50)
+
+            keyguardRepository.topClippingBounds.value = 1000
+            assertThat(topClippingBounds).isEqualTo(1000)
+        }
+
+    @Test
     fun alpha_glanceableHubOpen_isZero() =
         testScope.runTest {
             val alpha by collectLastValue(underTest.alpha)
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 033f93b..ad30317 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -477,9 +477,13 @@
     public void dump(PrintWriter pw, String[] args) {
         pw.println("KeyguardClockSwitch:");
         pw.println("  mSmallClockFrame = " + mSmallClockFrame);
-        pw.println("  mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+        if (mSmallClockFrame != null) {
+            pw.println("  mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha());
+        }
         pw.println("  mLargeClockFrame = " + mLargeClockFrame);
-        pw.println("  mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+        if (mLargeClockFrame != null) {
+            pw.println("  mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha());
+        }
         pw.println("  mStatusArea = " + mStatusArea);
         pw.println("  mDisplayedClockSize = " + mDisplayedClockSize);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index d012d24..1437194 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -132,6 +132,9 @@
      */
     val isDozing: StateFlow<Boolean>
 
+    /** Keyguard can be clipped at the top as the shade is dragged */
+    val topClippingBounds: MutableStateFlow<Int?>
+
     /**
      * Observable for whether the device is dreaming.
      *
@@ -326,6 +329,8 @@
     private val _clockShouldBeCentered = MutableStateFlow(true)
     override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered.asStateFlow()
 
+    override val topClippingBounds = MutableStateFlow<Int?>(null)
+
     override val isKeyguardShowing: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 6170356..91747e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -171,6 +171,14 @@
     /** Whether the keyguard is going away. */
     val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
 
+    /** Keyguard can be clipped at the top as the shade is dragged */
+    val topClippingBounds: Flow<Int?> =
+        combine(configurationInteractor.onAnyConfigurationChange, repository.topClippingBounds) {
+            _,
+            topClippingBounds ->
+            topClippingBounds
+        }
+
     /** Last point that [KeyguardRootView] view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
 
@@ -328,6 +336,10 @@
         repository.keyguardDoneAnimationsFinished()
     }
 
+    fun setTopClippingBounds(top: Int?) {
+        repository.topClippingBounds.value = top
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index 2aebd99..48092c6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -21,6 +21,7 @@
 import android.annotation.DrawableRes
 import android.annotation.SuppressLint
 import android.graphics.Point
+import android.graphics.Rect
 import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
@@ -158,6 +159,23 @@
                         }
 
                         launch {
+                            val clipBounds = Rect()
+                            viewModel.topClippingBounds.collect { clipTop ->
+                                if (clipTop == null) {
+                                    view.setClipBounds(null)
+                                } else {
+                                    clipBounds.apply {
+                                        top = clipTop
+                                        left = view.getLeft()
+                                        right = view.getRight()
+                                        bottom = view.getBottom()
+                                    }
+                                    view.setClipBounds(clipBounds)
+                                }
+                            }
+                        }
+
+                        launch {
                             viewModel.lockscreenStateAlpha.collect { alpha ->
                                 childViews[statusViewId]?.alpha = alpha
                             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 5d36da9..ea66702 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -80,6 +80,12 @@
     val notificationBounds: StateFlow<NotificationContainerBounds> =
         keyguardInteractor.notificationContainerBounds
 
+    /**
+     * The keyguard root view can be clipped as the shade is pulled down, typically only for
+     * non-split shade cases.
+     */
+    val topClippingBounds: Flow<Int?> = keyguardInteractor.topClippingBounds
+
     /** An observable for the alpha level for the entire keyguard root view. */
     val alpha: Flow<Float> =
         merge(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 46806e6..54b6ad7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -790,7 +790,7 @@
 
 /** Mutates the HeadsUp state of notifications. */
 private interface HunMutator {
-    fun updateNotification(key: String, alert: Boolean)
+    fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
     fun removeNotification(key: String, releaseImmediately: Boolean)
 }
 
@@ -801,8 +801,8 @@
 private class HunMutatorImpl(private val headsUpManager: HeadsUpManager) : HunMutator {
     private val deferred = mutableListOf<Pair<String, Boolean>>()
 
-    override fun updateNotification(key: String, alert: Boolean) {
-        headsUpManager.updateNotification(key, alert)
+    override fun updateNotification(key: String, shouldHeadsUpAgain: Boolean) {
+        headsUpManager.updateNotification(key, shouldHeadsUpAgain)
     }
 
     override fun removeNotification(key: String, releaseImmediately: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 380cdad..ae4ba27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -18,6 +18,7 @@
 
 import android.os.UserHandle
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.screenshareNotificationHiding
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.StatusBarState
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import dagger.Binds
 import dagger.Module
@@ -55,6 +57,8 @@
     private val statusBarStateController: StatusBarStateController,
     private val keyguardStateController: KeyguardStateController,
     private val selectedUserInteractor: SelectedUserInteractor,
+    private val sensitiveNotificationProtectionController:
+        SensitiveNotificationProtectionController,
 ) : Invalidator("SensitiveContentInvalidator"),
         SensitiveContentCoordinator,
         DynamicPrivacyController.Listener,
@@ -82,10 +86,13 @@
             return
         }
 
+        val isSensitiveContentProtectionActive = screenshareNotificationHiding() &&
+            sensitiveNotificationProtectionController.isSensitiveStateActive
         val currentUserId = lockscreenUserManager.currentUserId
         val devicePublic = lockscreenUserManager.isLockscreenPublicMode(currentUserId)
-        val deviceSensitive = devicePublic &&
-                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)
+        val deviceSensitive = (devicePublic &&
+                !lockscreenUserManager.userAllowsPrivateNotificationsInPublic(currentUserId)) ||
+                isSensitiveContentProtectionActive
         val dynamicallyUnlocked = dynamicPrivacyController.isDynamicallyUnlocked
         for (entry in extractAllRepresentativeEntries(entries).filter { it.rowExists() }) {
             val notifUserId = entry.sbn.user.identifier
@@ -105,9 +112,13 @@
                     else -> lockscreenUserManager.needsSeparateWorkChallenge(notifUserId)
                 }
             }
+
+            val shouldProtectNotification = screenshareNotificationHiding() &&
+                sensitiveNotificationProtectionController.shouldProtectNotification(entry)
+
             val needsRedaction = lockscreenUserManager.needsRedaction(entry)
             val isSensitive = userPublic && needsRedaction
-            entry.setSensitive(isSensitive, deviceSensitive)
+            entry.setSensitive(isSensitive || shouldProtectNotification, deviceSensitive)
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.kt
new file mode 100644
index 0000000..a21dd9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationAvalancheSuppression.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.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for reading or using the notification avalanche suppression flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object NotificationAvalancheSuppression {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_SUPPRESSION
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.notificationAvalancheSuppression()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+            RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 6a66bb7..9a8cc0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -22,6 +22,7 @@
 import static com.android.app.animation.Interpolators.STANDARD;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
 import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
+import static com.android.systemui.Flags.screenshareNotificationHiding;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnEmptySpaceClickListener;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.OnOverscrollTopChangedListener;
@@ -135,6 +136,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
@@ -218,6 +220,8 @@
     private final SecureSettings mSecureSettings;
     private final NotificationDismissibilityProvider mDismissibilityProvider;
     private final ActivityStarter mActivityStarter;
+    private final SensitiveNotificationProtectionController
+            mSensitiveNotificationProtectionController;
 
     private View mLongPressedView;
 
@@ -295,6 +299,15 @@
                 }
             };
 
+    private final Runnable mSensitiveStateChangedListener = new Runnable() {
+        @Override
+        public void run() {
+            // Animate false to protect against screen recording capturing content
+            // during the animation
+            updateSensitivenessWithAnimation(false);
+        }
+    };
+
     private final DynamicPrivacyController.Listener mDynamicPrivacyControllerListener = () -> {
         if (mView.isExpanded()) {
             // The bottom might change because we're using the final actual height of the view
@@ -399,7 +412,20 @@
     }
 
     private void updateSensitivenessWithAnimation(boolean animate) {
-        mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+        Trace.beginSection("NSSLC.updateSensitivenessWithAnimation");
+        if (screenshareNotificationHiding()) {
+            boolean isAnyProfilePublic = mLockscreenUserManager.isAnyProfilePublicMode();
+            boolean isSensitiveContentProtectionActive =
+                    mSensitiveNotificationProtectionController.isSensitiveStateActive();
+            boolean isSensitive = isAnyProfilePublic || isSensitiveContentProtectionActive;
+
+            // Only animate if in a non-sensitive state (not screen sharing)
+            boolean shouldAnimate = animate && !isSensitiveContentProtectionActive;
+            mView.updateSensitiveness(shouldAnimate, isSensitive);
+        } else {
+            mView.updateSensitiveness(animate, mLockscreenUserManager.isAnyProfilePublicMode());
+        }
+        Trace.endSection();
     }
 
     /**
@@ -708,7 +734,8 @@
             SecureSettings secureSettings,
             NotificationDismissibilityProvider dismissibilityProvider,
             ActivityStarter activityStarter,
-            SplitShadeStateController splitShadeStateController) {
+            SplitShadeStateController splitShadeStateController,
+            SensitiveNotificationProtectionController sensitiveNotificationProtectionController) {
         mView = view;
         mKeyguardTransitionRepo = keyguardTransitionRepo;
         mViewBinder = viewBinder;
@@ -756,6 +783,7 @@
         mSecureSettings = secureSettings;
         mDismissibilityProvider = dismissibilityProvider;
         mActivityStarter = activityStarter;
+        mSensitiveNotificationProtectionController = sensitiveNotificationProtectionController;
         mView.passSplitShadeStateController(splitShadeStateController);
         mDumpManager.registerDumpable(this);
         updateResources();
@@ -860,6 +888,11 @@
         mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
         mDeviceProvisionedListener.onDeviceProvisionedChanged();
 
+        if (screenshareNotificationHiding()) {
+            mSensitiveNotificationProtectionController
+                    .registerSensitiveStateListener(mSensitiveStateChangedListener);
+        }
+
         if (mView.isAttachedToWindow()) {
             mOnAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index ae04eaf..459b368 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -60,6 +60,7 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.ScrimAlpha;
 import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -217,6 +218,7 @@
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final KeyguardInteractor mKeyguardInteractor;
 
     private GradientColors mColors;
     private boolean mNeedsDrawableColorUpdate;
@@ -311,6 +313,7 @@
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
             AlternateBouncerToGoneTransitionViewModel alternateBouncerToGoneTransitionViewModel,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            KeyguardInteractor keyguardInteractor,
             WallpaperRepository wallpaperRepository,
             @Main CoroutineDispatcher mainDispatcher,
             LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
@@ -357,6 +360,7 @@
         mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel;
         mAlternateBouncerToGoneTransitionViewModel = alternateBouncerToGoneTransitionViewModel;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
+        mKeyguardInteractor = keyguardInteractor;
         mWallpaperRepository = wallpaperRepository;
         mMainDispatcher = mainDispatcher;
     }
@@ -759,7 +763,9 @@
             // see: b/186644628
             mNotificationsScrim.setDrawableBounds(left - 1, top, right + 1, bottom);
             mScrimBehind.setBottomEdgePosition((int) top);
+            mKeyguardInteractor.setTopClippingBounds((int) top);
         } else {
+            mKeyguardInteractor.setTopClippingBounds(null);
             mNotificationsScrim.setDrawableBounds(left, top, right, bottom);
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 1528c9b..1414150 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -159,7 +159,7 @@
     public void showNotification(@NonNull NotificationEntry entry) {
         mLogger.logShowNotification(entry);
         addEntry(entry);
-        updateNotification(entry.getKey(), true /* show */);
+        updateNotification(entry.getKey(), true /* shouldHeadsUpAgain */);
         entry.setInterruption();
     }
 
@@ -190,12 +190,12 @@
     /**
      * Called when the notification state has been updated.
      * @param key the key of the entry that was updated
-     * @param show whether the notification should show again and force reevaluation of
-     *              removal time
+     * @param shouldHeadsUpAgain whether the notification should show again and force reevaluation
+     *                           of removal time
      */
-    public void updateNotification(@NonNull String key, boolean show) {
+    public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) {
         HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
-        mLogger.logUpdateNotification(key, show, headsUpEntry != null);
+        mLogger.logUpdateNotification(key, shouldHeadsUpAgain, headsUpEntry != null);
         if (headsUpEntry == null) {
             // the entry was released before this update (i.e by a listener) This can happen
             // with the groupmanager
@@ -204,7 +204,7 @@
 
         headsUpEntry.mEntry.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
 
-        if (show) {
+        if (shouldHeadsUpAgain) {
             headsUpEntry.updateEntry(true /* updatePostTime */, "updateNotification");
             if (headsUpEntry != null) {
                 setEntryPinned(headsUpEntry, shouldHeadsUpBecomePinned(headsUpEntry.mEntry));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index b8c7e20..a7352be 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -182,7 +182,7 @@
      */
     fun unpinAll(userUnPinned: Boolean)
 
-    fun updateNotification(key: String, alert: Boolean)
+    fun updateNotification(key: String, shouldHeadsUpAgain: Boolean)
 }
 
 /** Sets the animation state of the HeadsUpManager. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
new file mode 100644
index 0000000..970cc75
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionController.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A controller which provides the current sensitive notification protections status as well as
+ * to assist in feature usage and exemptions
+ */
+public interface SensitiveNotificationProtectionController {
+    /**
+     * Register a runnable that triggers on changes to protection state
+     *
+     * <p> onSensitiveStateChanged not invoked on registration
+     */
+    void registerSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+    /** Unregister a previously registered onSensitiveStateChanged runnable */
+    void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged);
+
+    /** Return {@code true} if device in state in which notifications should be protected */
+    boolean isSensitiveStateActive();
+
+    /** Return {@code true} when notification should be protected */
+    boolean shouldProtectNotification(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
new file mode 100644
index 0000000..3c4ca44
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy;
+
+import static com.android.systemui.Flags.screenshareNotificationHiding;
+
+import android.media.projection.MediaProjectionInfo;
+import android.media.projection.MediaProjectionManager;
+import android.os.Handler;
+import android.os.Trace;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.util.ListenerSet;
+
+import javax.inject.Inject;
+
+/** Implementation of SensitiveNotificationProtectionController. **/
+@SysUISingleton
+public class SensitiveNotificationProtectionControllerImpl
+        implements SensitiveNotificationProtectionController {
+    private final MediaProjectionManager mMediaProjectionManager;
+    private final ListenerSet<Runnable> mListeners = new ListenerSet<>();
+    private volatile MediaProjectionInfo mProjection;
+
+    @VisibleForTesting
+    final MediaProjectionManager.Callback mMediaProjectionCallback =
+            new MediaProjectionManager.Callback() {
+                @Override
+                public void onStart(MediaProjectionInfo info) {
+                    Trace.beginSection(
+                            "SNPC.onProjectionStart");
+                    mProjection = info;
+                    mListeners.forEach(Runnable::run);
+                    Trace.endSection();
+                }
+
+                @Override
+                public void onStop(MediaProjectionInfo info) {
+                    Trace.beginSection(
+                            "SNPC.onProjectionStop");
+                    mProjection = null;
+                    mListeners.forEach(Runnable::run);
+                    Trace.endSection();
+                }
+            };
+
+    @Inject
+    public SensitiveNotificationProtectionControllerImpl(
+            MediaProjectionManager mediaProjectionManager,
+            @Main Handler mainHandler) {
+        mMediaProjectionManager = mediaProjectionManager;
+
+        if (screenshareNotificationHiding()) {
+            mMediaProjectionManager.addCallback(mMediaProjectionCallback, mainHandler);
+        }
+    }
+
+    @Override
+    public void registerSensitiveStateListener(Runnable onSensitiveStateChanged) {
+        mListeners.addIfAbsent(onSensitiveStateChanged);
+    }
+
+    @Override
+    public void unregisterSensitiveStateListener(Runnable onSensitiveStateChanged) {
+        mListeners.remove(onSensitiveStateChanged);
+    }
+
+    @Override
+    public boolean isSensitiveStateActive() {
+        // TODO(b/316955558): Add disabled by developer option
+        // TODO(b/316955306): Add feature exemption for sysui and bug handlers
+        // TODO(b/316955346): Add feature exemption for single app screen sharing
+        return mProjection != null;
+    }
+
+    @Override
+    public boolean shouldProtectNotification(NotificationEntry entry) {
+        if (!isSensitiveStateActive()) {
+            return false;
+        }
+
+        // Exempt foreground service notifications from protection in effort to keep screen share
+        // stop actions easily accessible
+        // TODO(b/316955208): Exempt FGS notifications only for app that started projection
+        return !entry.getSbn().getNotification().isFgsOrUij();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index 3304b98..15200bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -60,6 +60,8 @@
 import com.android.systemui.statusbar.policy.RotationLockControllerImpl;
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.statusbar.policy.SecurityControllerImpl;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionControllerImpl;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.statusbar.policy.SplitShadeStateControllerImpl;
 import com.android.systemui.statusbar.policy.UserInfoController;
@@ -146,6 +148,11 @@
 
     /** */
     @Binds
+    SensitiveNotificationProtectionController provideSensitiveNotificationProtectionController(
+            SensitiveNotificationProtectionControllerImpl controllerImpl);
+
+    /** */
+    @Binds
     UserInfoController provideUserInfoContrller(UserInfoControllerImpl controllerImpl);
 
     /** */
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index df547ae..350ed2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -17,9 +17,11 @@
 package com.android.systemui.statusbar.notification.collection.coordinator
 
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import android.service.notification.StatusBarNotification
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
@@ -33,6 +35,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
@@ -55,28 +58,31 @@
     val statusBarStateController: StatusBarStateController = mock()
     val keyguardStateController: KeyguardStateController = mock()
     val mSelectedUserInteractor: SelectedUserInteractor = mock()
+    val sensitiveNotificationProtectionController: SensitiveNotificationProtectionController =
+        mock()
 
     val coordinator: SensitiveContentCoordinator =
-        DaggerTestSensitiveContentCoordinatorComponent
-                .factory()
-                .create(
-                        dynamicPrivacyController,
-                        lockscreenUserManager,
-                        keyguardUpdateMonitor,
-                        statusBarStateController,
-                        keyguardStateController,
-                        mSelectedUserInteractor)
-                .coordinator
+        DaggerTestSensitiveContentCoordinatorComponent.factory()
+            .create(
+                dynamicPrivacyController,
+                lockscreenUserManager,
+                keyguardUpdateMonitor,
+                statusBarStateController,
+                keyguardStateController,
+                mSelectedUserInteractor,
+                sensitiveNotificationProtectionController
+            )
+            .coordinator
 
     @Test
     fun onDynamicPrivacyChanged_invokeInvalidationListener() {
         coordinator.attach(pipeline)
-        val invalidator = withArgCaptor<Invalidator> {
-            verify(pipeline).addPreRenderInvalidator(capture())
-        }
-        val dynamicPrivacyListener = withArgCaptor<DynamicPrivacyController.Listener> {
-            verify(dynamicPrivacyController).addListener(capture())
-        }
+        val invalidator =
+            withArgCaptor<Invalidator> { verify(pipeline).addPreRenderInvalidator(capture()) }
+        val dynamicPrivacyListener =
+            withArgCaptor<DynamicPrivacyController.Listener> {
+                verify(dynamicPrivacyController).addListener(capture())
+            }
 
         val invalidationListener = mock<Pluggable.PluggableListener<Invalidator>>()
         invalidator.setInvalidationListener(invalidationListener)
@@ -89,9 +95,10 @@
     @Test
     fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -105,11 +112,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifDoesNotNeedRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, false)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
@@ -123,11 +178,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceUnlocked_notifWouldNeedRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(false)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, false)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -141,11 +244,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_userAllowsPublicNotifs_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(true)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, false)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -159,11 +310,61 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceLocked_userDisallowsPublicNotifs_notifDoesNotNeedRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, false)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceLocked_notifNeedsRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -177,11 +378,59 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceLocked_notifNeedsRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(false)
+        val entry = fakeNotification(1, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -195,18 +444,66 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        val entry = fakeNotification(1, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(false, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifNeedsRedaction_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        val entry = fakeNotification(1, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
         whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
         whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
-
         val entry = fakeNotification(2, true)
 
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
@@ -215,11 +512,62 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_sensitiveActive() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+        val entry = fakeNotification(2, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    @Suppress("ktlint:standard:max-line-length")
+    fun onBeforeRenderList_deviceDynamicallyUnlocked_notifUserNeedsWorkChallenge_shouldProtectNotification() {
+        coordinator.attach(pipeline)
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
+
+        whenever(lockscreenUserManager.currentUserId).thenReturn(1)
+        whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
+        whenever(lockscreenUserManager.userAllowsPrivateNotificationsInPublic(1)).thenReturn(false)
+        whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
+        whenever(lockscreenUserManager.needsSeparateWorkChallenge(2)).thenReturn(true)
+        val entry = fakeNotification(2, true)
+        whenever(
+                sensitiveNotificationProtectionController.shouldProtectNotification(
+                    entry.getRepresentativeEntry()
+                )
+            )
+            .thenReturn(true)
+
+        onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
+
+        verify(entry.representativeEntry!!).setSensitive(true, true)
+    }
+
+    @Test
     fun onBeforeRenderList_deviceDynamicallyUnlocked_deviceBiometricBypassingLockScreen() {
         coordinator.attach(pipeline)
-        val onBeforeRenderListListener = withArgCaptor<OnBeforeRenderListListener> {
-            verify(pipeline).addOnBeforeRenderListListener(capture())
-        }
+        val onBeforeRenderListListener =
+            withArgCaptor<OnBeforeRenderListListener> {
+                verify(pipeline).addOnBeforeRenderListListener(capture())
+            }
 
         whenever(lockscreenUserManager.currentUserId).thenReturn(1)
         whenever(lockscreenUserManager.isLockscreenPublicMode(1)).thenReturn(true)
@@ -227,9 +575,11 @@
         whenever(dynamicPrivacyController.isDynamicallyUnlocked).thenReturn(true)
         whenever(statusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD)
         whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometricAndIsBypassing(any()))
-                .thenReturn(true)
-
+            .thenReturn(true)
         val entry = fakeNotification(2, true)
+        whenever(sensitiveNotificationProtectionController.isSensitiveStateActive).thenReturn(true)
+        whenever(sensitiveNotificationProtectionController.shouldProtectNotification(any()))
+            .thenReturn(true)
 
         onBeforeRenderListListener.onBeforeRenderList(listOf(entry))
 
@@ -237,15 +587,11 @@
     }
 
     private fun fakeNotification(notifUserId: Int, needsRedaction: Boolean): ListEntry {
-        val mockUserHandle = mock<UserHandle>().apply {
-            whenever(identifier).thenReturn(notifUserId)
-        }
-        val mockSbn: StatusBarNotification = mock<StatusBarNotification>().apply {
-            whenever(user).thenReturn(mockUserHandle)
-        }
-        val mockEntry = mock<NotificationEntry>().apply {
-            whenever(sbn).thenReturn(mockSbn)
-        }
+        val mockUserHandle =
+            mock<UserHandle>().apply { whenever(identifier).thenReturn(notifUserId) }
+        val mockSbn: StatusBarNotification =
+            mock<StatusBarNotification>().apply { whenever(user).thenReturn(mockUserHandle) }
+        val mockEntry = mock<NotificationEntry>().apply { whenever(sbn).thenReturn(mockSbn) }
         whenever(lockscreenUserManager.needsRedaction(mockEntry)).thenReturn(needsRedaction)
         whenever(mockEntry.rowExists()).thenReturn(true)
         return object : ListEntry("key", 0) {
@@ -268,6 +614,8 @@
             @BindsInstance statusBarStateController: StatusBarStateController,
             @BindsInstance keyguardStateController: KeyguardStateController,
             @BindsInstance selectedUserInteractor: SelectedUserInteractor,
+            @BindsInstance
+            sensitiveNotificationProtectionController: SensitiveNotificationProtectionController,
         ): TestSensitiveContentCoordinatorComponent
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 89f826b..1ab4c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack;
 
+import static com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING;
 import static com.android.systemui.log.LogBufferHelperKt.logcatLogBuffer;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
@@ -32,6 +33,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
@@ -39,6 +41,7 @@
 
 import android.metrics.LogMaker;
 import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.View;
@@ -101,6 +104,7 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController;
+import com.android.systemui.statusbar.policy.SensitiveNotificationProtectionController;
 import com.android.systemui.statusbar.policy.ZenModeController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.util.settings.SecureSettings;
@@ -172,10 +176,16 @@
     @Mock private ActivityStarter mActivityStarter;
     @Mock private KeyguardTransitionRepository mKeyguardTransitionRepo;
     @Mock private NotificationListViewBinder mViewBinder;
+    @Mock
+    private SensitiveNotificationProtectionController mSensitiveNotificationProtectionController;
+
+    @Captor
+    private ArgumentCaptor<Runnable> mSensitiveStateListenerArgumentCaptor;
 
     @Captor
     private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerArgumentCaptor;
 
+
     private final ActiveNotificationListRepository mActiveNotificationsRepository =
             new ActiveNotificationListRepository();
 
@@ -386,6 +396,23 @@
     }
 
     @Test
+    public void testOnUserChange_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
     public void testOnUserChange_verifySensitiveProfile() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
         initController(/* viewIsAttached= */ true);
@@ -403,6 +430,80 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnUserChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnUserChange_verifySensitiveProfile_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnUserChange_verifySensitiveActive_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+        initController(/* viewIsAttached= */ true);
+
+        ArgumentCaptor<UserChangedListener> userChangedCaptor = ArgumentCaptor
+                .forClass(UserChangedListener.class);
+
+        verify(mNotificationLockscreenUserManager)
+                .addUserChangedListener(userChangedCaptor.capture());
+        reset(mNotificationStackScrollLayout);
+
+        UserChangedListener changedListener = userChangedCaptor.getValue();
+        changedListener.onUserChanged(0);
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    public void testOnStatePostChange_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
     public void testOnStatePostChange_verifyIfProfileIsPublic() {
         when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
 
@@ -418,6 +519,194 @@
     }
 
     @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_verifyNotSensitive_screenshareNotificationHidingEnabled() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_verifyIfProfileIsPublic_screenshareNotificationHidingEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_verifyIfSensitiveActive_screenshareNotificationHidingEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    public void testOnStatePostChange_goingFullShade_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+    }
+
+    @Test
+    public void testOnStatePostChange_goingFullShade_verifyIfProfileIsPublic() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_goingFullShade_verifyNotSensitive_screenshareHideEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_goingFullShade_verifyProfileIsPublic_screenshareHideEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(true, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnStatePostChange_goingFullShade_verifySensitiveActive_screenshareHideEnabled(
+    ) {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSysuiStatusBarStateController.goingToFullShade()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSysuiStatusBarStateController).addCallback(
+                mStateListenerArgumentCaptor.capture(), anyInt());
+
+        StatusBarStateController.StateListener stateListener =
+                mStateListenerArgumentCaptor.getValue();
+
+        stateListener.onStatePostChange();
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnProjectionStateChanged_verifyNotSensitive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive())
+                .thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController)
+                .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+        mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, false);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnProjectionStateChanged_verifyIfProfileIsPublic() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(true);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(false);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController)
+                .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+        mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void testOnProjectionStateChanged_verifyIfSensitiveActive() {
+        when(mNotificationLockscreenUserManager.isAnyProfilePublicMode()).thenReturn(false);
+        when(mSensitiveNotificationProtectionController.isSensitiveStateActive()).thenReturn(true);
+
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController)
+                .registerSensitiveStateListener(mSensitiveStateListenerArgumentCaptor.capture());
+
+        mSensitiveStateListenerArgumentCaptor.getValue().run();
+
+        verify(mNotificationStackScrollLayout).updateSensitiveness(false, true);
+    }
+
+    @Test
     public void testOnMenuShownLogging() {
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class, RETURNS_DEEP_STUBS);
         when(row.getEntry().getSbn().getLogMaker()).thenReturn(new LogMaker(
@@ -666,6 +955,20 @@
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
     }
 
+    @Test
+    @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void sensitiveNotificationProtectionControllerListenerNotRegistered() {
+        initController(/* viewIsAttached= */ true);
+        verifyZeroInteractions(mSensitiveNotificationProtectionController);
+    }
+
+    @Test
+    @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+    public void sensitiveNotificationProtectionControllerListenerRegistered() {
+        initController(/* viewIsAttached= */ true);
+        verify(mSensitiveNotificationProtectionController).registerSensitiveStateListener(any());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
@@ -744,7 +1047,8 @@
                 mSecureSettings,
                 mock(NotificationDismissibilityProvider.class),
                 mActivityStarter,
-                new ResourcesSplitShadeStateController());
+                new ResourcesSplitShadeStateController(),
+                mSensitiveNotificationProtectionController);
     }
 
     static class LogMatcher implements ArgumentMatcher<LogMaker> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 4827c92..d9eaea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -66,6 +66,7 @@
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.shared.model.KeyguardState;
 import com.android.systemui.keyguard.shared.model.TransitionState;
@@ -145,6 +146,7 @@
     @Mock private AlternateBouncerToGoneTransitionViewModel
             mAlternateBouncerToGoneTransitionViewModel;
     @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
+    @Mock private KeyguardInteractor mKeyguardInteractor;
     private final FakeWallpaperRepository mWallpaperRepository = new FakeWallpaperRepository();
     @Mock private CoroutineDispatcher mMainDispatcher;
     @Mock private TypedArray mMockTypedArray;
@@ -292,6 +294,7 @@
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mKeyguardInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
                 mLinearLargeScreenShadeInterpolator);
@@ -1000,6 +1003,7 @@
                 mPrimaryBouncerToGoneTransitionViewModel,
                 mAlternateBouncerToGoneTransitionViewModel,
                 mKeyguardTransitionInteractor,
+                mKeyguardInteractor,
                 mWallpaperRepository,
                 mMainDispatcher,
                 mLinearLargeScreenShadeInterpolator);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
new file mode 100644
index 0000000..cd5d5ed
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.app.Notification
+import android.media.projection.MediaProjectionInfo
+import android.media.projection.MediaProjectionManager
+import android.os.Handler
+import android.service.notification.StatusBarNotification
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SensitiveNotificationProtectionControllerTest : SysuiTestCase() {
+    @Mock private lateinit var handler: Handler
+
+    @Mock private lateinit var mediaProjectionManager: MediaProjectionManager
+
+    @Mock private lateinit var mediaProjectionInfo: MediaProjectionInfo
+
+    @Mock private lateinit var listener1: Runnable
+    @Mock private lateinit var listener2: Runnable
+    @Mock private lateinit var listener3: Runnable
+
+    @Captor
+    private lateinit var mediaProjectionCallbackCaptor:
+        ArgumentCaptor<MediaProjectionManager.Callback>
+
+    private lateinit var controller: SensitiveNotificationProtectionControllerImpl
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        mSetFlagsRule.enableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+
+        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+        // Obtain useful MediaProjectionCallback
+        verify(mediaProjectionManager).addCallback(mediaProjectionCallbackCaptor.capture(), any())
+    }
+
+    @Test
+    fun init_flagEnabled_registerMediaProjectionManagerCallback() {
+        assertNotNull(mediaProjectionCallbackCaptor.value)
+    }
+
+    @Test
+    fun init_flagDisabled_noRegisterMediaProjectionManagerCallback() {
+        mSetFlagsRule.disableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING)
+        reset(mediaProjectionManager)
+
+        controller = SensitiveNotificationProtectionControllerImpl(mediaProjectionManager, handler)
+
+        verifyZeroInteractions(mediaProjectionManager)
+    }
+
+    @Test
+    fun registerSensitiveStateListener_singleListener() {
+        controller.registerSensitiveStateListener(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+    }
+
+    @Test
+    fun registerSensitiveStateListener_multipleListeners() {
+        controller.registerSensitiveStateListener(listener1)
+        controller.registerSensitiveStateListener(listener2)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+        verify(listener2, times(2)).run()
+    }
+
+    @Test
+    fun registerSensitiveStateListener_afterProjectionActive() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        controller.registerSensitiveStateListener(listener1)
+        verifyZeroInteractions(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1).run()
+    }
+
+    @Test
+    fun unregisterSensitiveStateListener_singleListener() {
+        controller.registerSensitiveStateListener(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+
+        controller.unregisterSensitiveStateListener(listener1)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verifyNoMoreInteractions(listener1)
+    }
+
+    @Test
+    fun unregisterSensitiveStateListener_multipleListeners() {
+        controller.registerSensitiveStateListener(listener1)
+        controller.registerSensitiveStateListener(listener2)
+        controller.registerSensitiveStateListener(listener3)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verify(listener1, times(2)).run()
+        verify(listener2, times(2)).run()
+        verify(listener3, times(2)).run()
+
+        controller.unregisterSensitiveStateListener(listener1)
+        controller.unregisterSensitiveStateListener(listener2)
+
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        verifyNoMoreInteractions(listener1)
+        verifyNoMoreInteractions(listener2)
+        verify(listener3, times(4)).run()
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionInactive_false() {
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionActive_true() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        assertTrue(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionInactiveAfterActive_false() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+
+        assertFalse(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun isSensitiveStateActive_projectionActiveAfterInactive_true() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStop(mediaProjectionInfo)
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        assertTrue(controller.isSensitiveStateActive)
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionInactive_false() {
+        val notificationEntry = mock(NotificationEntry::class.java)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_fgsNotification_false() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        val notificationEntry = mock(NotificationEntry::class.java)
+        val sbn = mock(StatusBarNotification::class.java)
+        val notification = mock(Notification::class.java)
+        `when`(notificationEntry.sbn).thenReturn(sbn)
+        `when`(sbn.notification).thenReturn(notification)
+        `when`(notification.isFgsOrUij).thenReturn(true)
+
+        assertFalse(controller.shouldProtectNotification(notificationEntry))
+    }
+
+    @Test
+    fun shouldProtectNotification_projectionActive_notFgsNotification_true() {
+        mediaProjectionCallbackCaptor.value.onStart(mediaProjectionInfo)
+
+        val notificationEntry = mock(NotificationEntry::class.java)
+        val sbn = mock(StatusBarNotification::class.java)
+        val notification = mock(Notification::class.java)
+        `when`(notificationEntry.sbn).thenReturn(sbn)
+        `when`(sbn.notification).thenReturn(notification)
+        `when`(notification.isFgsOrUij).thenReturn(false)
+
+        assertTrue(controller.shouldProtectNotification(notificationEntry))
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 59f56dd..5766f7a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -130,6 +130,8 @@
     private val _isEncryptedOrLockdown = MutableStateFlow(true)
     override val isEncryptedOrLockdown: Flow<Boolean> = _isEncryptedOrLockdown
 
+    override val topClippingBounds = MutableStateFlow<Int?>(null)
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 3483c1a..a493d7a 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -95,6 +95,7 @@
     private static final int ALLOW_OVERRIDE_APP_RESTRICTIONS = 0x100;
     private static final int ALLOW_IMPLICIT_BROADCASTS = 0x200;
     private static final int ALLOW_VENDOR_APEX = 0x400;
+    private static final int ALLOW_SIGNATURE_PERMISSIONS = 0x800;
     private static final int ALLOW_ALL = ~0;
 
     // property for runtime configuration differentiation
@@ -597,7 +598,7 @@
 
         // Vendors are only allowed to customize these
         int vendorPermissionFlag = ALLOW_LIBS | ALLOW_FEATURES | ALLOW_PRIVAPP_PERMISSIONS
-                | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
+                | ALLOW_SIGNATURE_PERMISSIONS | ALLOW_ASSOCIATIONS | ALLOW_VENDOR_APEX;
         if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.O_MR1) {
             // For backward compatibility
             vendorPermissionFlag |= (ALLOW_PERMISSIONS | ALLOW_APP_CONFIGS);
@@ -649,9 +650,9 @@
         // TODO(b/157203468): ALLOW_HIDDENAPI_WHITELISTING must be removed because we prohibited
         // the use of hidden APIs from the product partition.
         int productPermissionFlag = ALLOW_FEATURES | ALLOW_LIBS | ALLOW_PERMISSIONS
-                | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_HIDDENAPI_WHITELISTING
-                | ALLOW_ASSOCIATIONS | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS
-                | ALLOW_VENDOR_APEX;
+                | ALLOW_APP_CONFIGS | ALLOW_PRIVAPP_PERMISSIONS | ALLOW_SIGNATURE_PERMISSIONS
+                | ALLOW_HIDDENAPI_WHITELISTING | ALLOW_ASSOCIATIONS
+                | ALLOW_OVERRIDE_APP_RESTRICTIONS | ALLOW_IMPLICIT_BROADCASTS | ALLOW_VENDOR_APEX;
         if (Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.R) {
             // TODO(b/157393157): This must check product interface enforcement instead of
             // DEVICE_INITIAL_SDK_INT for the devices without product interface enforcement.
@@ -772,6 +773,8 @@
             final boolean allowAppConfigs = (permissionFlag & ALLOW_APP_CONFIGS) != 0;
             final boolean allowPrivappPermissions = (permissionFlag & ALLOW_PRIVAPP_PERMISSIONS)
                     != 0;
+            final boolean allowSignaturePermissions = (permissionFlag & ALLOW_SIGNATURE_PERMISSIONS)
+                    != 0;
             final boolean allowOemPermissions = (permissionFlag & ALLOW_OEM_PERMISSIONS) != 0;
             final boolean allowApiWhitelisting = (permissionFlag & ALLOW_HIDDENAPI_WHITELISTING)
                     != 0;
@@ -1246,6 +1249,38 @@
                             XmlUtils.skipCurrentTag(parser);
                         }
                     } break;
+                    case "signature-permissions": {
+                        if (allowSignaturePermissions) {
+                            // signature permissions from system, apex, vendor, product and
+                            // system_ext partitions are stored separately. This is to
+                            // prevent xml files in the vendor partition from granting
+                            // permissions to signature apps in the system partition and vice versa.
+                            boolean vendor = permFile.toPath().startsWith(
+                                    Environment.getVendorDirectory().toPath() + "/")
+                                    || permFile.toPath().startsWith(
+                                    Environment.getOdmDirectory().toPath() + "/");
+                            boolean product = permFile.toPath().startsWith(
+                                    Environment.getProductDirectory().toPath() + "/");
+                            boolean systemExt = permFile.toPath().startsWith(
+                                    Environment.getSystemExtDirectory().toPath() + "/");
+                            if (vendor) {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getVendorSignatureAppAllowlist());
+                            } else if (product) {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getProductSignatureAppAllowlist());
+                            } else if (systemExt) {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getSystemExtSignatureAppAllowlist());
+                            } else {
+                                readSignatureAppPermissions(parser,
+                                        mPermissionAllowlist.getSignatureAppAllowlist());
+                            }
+                        } else {
+                            logNotAllowedInPartition(name, permFile, parser);
+                            XmlUtils.skipCurrentTag(parser);
+                        }
+                    } break;
                     case "oem-permissions": {
                         if (allowOemPermissions) {
                             readOemPermissions(parser);
@@ -1655,6 +1690,12 @@
         readPermissionAllowlist(parser, allowlist, "privapp-permissions");
     }
 
+    private void readSignatureAppPermissions(@NonNull XmlPullParser parser,
+            @NonNull ArrayMap<String, ArrayMap<String, Boolean>> allowlist)
+            throws IOException, XmlPullParserException {
+        readPermissionAllowlist(parser, allowlist, "signature-permissions");
+    }
+
     private void readInstallInUserType(XmlPullParser parser,
             Map<String, Set<String>> doInstallMap,
             Map<String, Set<String>> nonInstallMap)
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index fb4943a..c1d5ebf 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1328,8 +1328,7 @@
             mPointerIconDisplayContext = null;
         }
 
-        updateAdditionalDisplayInputProperties(displayId,
-                AdditionalDisplayInputProperties::reset);
+        updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset);
 
         // TODO(b/320763728): Rely on WindowInfosListener to determine when a display has been
         //  removed in InputDispatcher instead of this callback.
@@ -1812,8 +1811,6 @@
             mPointerIconType = icon.getType();
             mPointerIcon = mPointerIconType == PointerIcon.TYPE_CUSTOM ? icon : null;
 
-            if (!mCurrentDisplayProperties.pointerIconVisible) return false;
-
             return mNative.setPointerIcon(icon, displayId, deviceId, pointerId, inputToken);
         }
     }
@@ -3509,7 +3506,11 @@
                 properties = new AdditionalDisplayInputProperties();
                 mAdditionalDisplayInputProperties.put(displayId, properties);
             }
+            final boolean oldPointerIconVisible = properties.pointerIconVisible;
             updater.accept(properties);
+            if (oldPointerIconVisible != properties.pointerIconVisible) {
+                mNative.setPointerIconVisibility(displayId, properties.pointerIconVisible);
+            }
             if (properties.allDefaults()) {
                 mAdditionalDisplayInputProperties.remove(displayId);
             }
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index c3ef80f..706db3d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -190,6 +190,8 @@
     boolean setPointerIcon(@NonNull PointerIcon icon, int displayId, int deviceId, int pointerId,
             @NonNull IBinder inputToken);
 
+    void setPointerIconVisibility(int displayId, boolean visible);
+
     void requestPointerCapture(IBinder windowToken, boolean enabled);
 
     boolean canDispatchToDisplay(int deviceId, int displayId);
@@ -452,6 +454,9 @@
                 int pointerId, IBinder inputToken);
 
         @Override
+        public native void setPointerIconVisibility(int displayId, boolean visible);
+
+        @Override
         public native void requestPointerCapture(IBinder windowToken, boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
index 3efac81..d138606 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionAllowlist.java
@@ -26,6 +26,7 @@
 public final class PermissionAllowlist {
     @NonNull
     private final ArrayMap<String, ArrayMap<String, Boolean>> mOemAppAllowlist = new ArrayMap<>();
+
     @NonNull
     private final ArrayMap<String, ArrayMap<String, Boolean>> mPrivilegedAppAllowlist =
             new ArrayMap<>();
@@ -43,6 +44,19 @@
             mApexPrivilegedAppAllowlists = new ArrayMap<>();
 
     @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mSignatureAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mVendorSignatureAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mProductSignatureAppAllowlist =
+            new ArrayMap<>();
+    @NonNull
+    private final ArrayMap<String, ArrayMap<String, Boolean>> mSystemExtSignatureAppAllowlist =
+            new ArrayMap<>();
+
+    @NonNull
     public ArrayMap<String, ArrayMap<String, Boolean>> getOemAppAllowlist() {
         return mOemAppAllowlist;
     }
@@ -73,6 +87,26 @@
         return mApexPrivilegedAppAllowlists;
     }
 
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getSignatureAppAllowlist() {
+        return mSignatureAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getVendorSignatureAppAllowlist() {
+        return mVendorSignatureAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getProductSignatureAppAllowlist() {
+        return mProductSignatureAppAllowlist;
+    }
+
+    @NonNull
+    public ArrayMap<String, ArrayMap<String, Boolean>> getSystemExtSignatureAppAllowlist() {
+        return mSystemExtSignatureAppAllowlist;
+    }
+
     @Nullable
     public Boolean getOemAppAllowlistState(@NonNull String packageName,
             @NonNull String permissionName) {
@@ -137,4 +171,44 @@
         }
         return permissions.get(permissionName);
     }
+
+    @Nullable
+    public Boolean getSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getVendorSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mVendorSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getProductSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mProductSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
+
+    @Nullable
+    public Boolean getSystemExtSignatureAppAllowlistState(@NonNull String packageName,
+            @NonNull String permissionName) {
+        ArrayMap<String, Boolean> permissions = mSystemExtSignatureAppAllowlist.get(packageName);
+        if (permissions == null) {
+            return null;
+        }
+        return permissions.get(permissionName);
+    }
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 1c90e30..ce0efe1 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -304,6 +304,7 @@
     bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
                         int32_t displayId, DeviceId deviceId, int32_t pointerId,
                         const sp<IBinder>& inputToken);
+    void setPointerIconVisibility(int32_t displayId, bool visible);
     void setMotionClassifierEnabled(bool enabled);
     std::optional<std::string> getBluetoothAddress(int32_t deviceId);
     void setStylusButtonMotionEventsEnabled(bool enabled);
@@ -1397,6 +1398,13 @@
     return mInputManager->getChoreographer().setPointerIcon(std::move(icon), displayId, deviceId);
 }
 
+void NativeInputManager::setPointerIconVisibility(int32_t displayId, bool visible) {
+    if (!ENABLE_POINTER_CHOREOGRAPHER) {
+        return;
+    }
+    mInputManager->getChoreographer().setPointerIconVisibility(displayId, visible);
+}
+
 TouchAffineTransformation NativeInputManager::getTouchAffineTransformation(
         JNIEnv *env, jfloatArray matrixArr) {
     ATRACE_CALL();
@@ -2550,6 +2558,13 @@
                               ibinderForJavaObject(env, inputTokenObj));
 }
 
+static void nativeSetPointerIconVisibility(JNIEnv* env, jobject nativeImplObj, jint displayId,
+                                           jboolean visible) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setPointerIconVisibility(displayId, visible);
+}
+
 static jboolean nativeCanDispatchToDisplay(JNIEnv* env, jobject nativeImplObj, jint deviceId,
                                            jint displayId) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2828,6 +2843,7 @@
          (void*)nativeSetCustomPointerIcon},
         {"setPointerIcon", "(Landroid/view/PointerIcon;IIILandroid/os/IBinder;)Z",
          (void*)nativeSetPointerIcon},
+        {"setPointerIconVisibility", "(IZ)V", (void*)nativeSetPointerIconVisibility},
         {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay},
         {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged},
         {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation},
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 62d2d7e..4c74878 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -22,6 +22,7 @@
 import android.content.pm.PermissionInfo
 import android.content.pm.SigningDetails
 import android.os.Build
+import android.permission.flags.Flags
 import android.util.Slog
 import com.android.internal.os.RoSystemProperties
 import com.android.internal.pm.permission.CompatibilityPermissionInfo
@@ -1197,15 +1198,80 @@
             newState.externalState.packageStates[PLATFORM_PACKAGE_NAME]!!
                 .androidPackage!!
                 .signingDetails
-        return sourceSigningDetails?.hasCommonSignerWithCapability(
-            packageSigningDetails,
-            SigningDetails.CertCapabilities.PERMISSION
-        ) == true ||
-            packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
-            platformSigningDetails.checkCapability(
+        val hasCommonSigner =
+            sourceSigningDetails?.hasCommonSignerWithCapability(
                 packageSigningDetails,
                 SigningDetails.CertCapabilities.PERMISSION
-            )
+            ) == true ||
+                packageSigningDetails.hasAncestorOrSelf(platformSigningDetails) ||
+                platformSigningDetails.checkCapability(
+                    packageSigningDetails,
+                    SigningDetails.CertCapabilities.PERMISSION
+                )
+        if (!Flags.signaturePermissionAllowlistEnabled()) {
+            return hasCommonSigner;
+        }
+        if (!hasCommonSigner) {
+            return false
+        }
+        // A platform signature permission also needs to be allowlisted on non-debuggable builds.
+        if (permission.packageName == PLATFORM_PACKAGE_NAME) {
+            val isRequestedByFactoryApp =
+                if (packageState.isSystem) {
+                    // For updated system applications, a signature permission still needs to be
+                    // allowlisted if it wasn't requested by the original application.
+                    if (packageState.isUpdatedSystemApp) {
+                        val disabledSystemPackage =
+                            newState.externalState.disabledSystemPackageStates[
+                                    packageState.packageName]
+                                ?.androidPackage
+                        disabledSystemPackage != null &&
+                            permission.name in disabledSystemPackage.requestedPermissions
+                    } else {
+                        true
+                    }
+                } else {
+                    false
+                }
+            if (
+                !(isRequestedByFactoryApp ||
+                    getSignaturePermissionAllowlistState(packageState, permission.name) == true)
+            ) {
+                Slog.w(
+                    LOG_TAG,
+                    "Signature permission ${permission.name} for package" +
+                        " ${packageState.packageName} (${packageState.path}) not in" +
+                        " signature permission allowlist"
+                )
+                if (!Build.isDebuggable()) {
+                    return false
+                }
+            }
+        }
+        return true
+    }
+
+    private fun MutateStateScope.getSignaturePermissionAllowlistState(
+        packageState: PackageState,
+        permissionName: String
+    ): Boolean? {
+        val permissionAllowlist = newState.externalState.permissionAllowlist
+        val packageName = packageState.packageName
+        return when {
+            packageState.isVendor || packageState.isOdm ->
+                permissionAllowlist.getVendorSignatureAppAllowlistState(packageName, permissionName)
+            packageState.isProduct ->
+                permissionAllowlist.getProductSignatureAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+            packageState.isSystemExt ->
+                permissionAllowlist.getSystemExtSignatureAppAllowlistState(
+                    packageName,
+                    permissionName
+                )
+            else -> permissionAllowlist.getSignatureAppAllowlistState(packageName, permissionName)
+        }
     }
 
     private fun MutateStateScope.checkPrivilegedPermissionAllowlist(
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 1c57623..29faed1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -132,8 +132,10 @@
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -563,6 +565,86 @@
         return Pair.create(splitPrimaryActivity, splitSecondActivity);
     }
 
+    /**
+     * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+     * while it is already on top, reports it as delivering to top.
+     */
+    @Test
+    public void testDesktopModeDeliverToTop() {
+        final ActivityStarter starter = prepareStarter(
+                FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP,
+                false /* mockGetRootTask */);
+        final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+
+        // Set focus back to the first task.
+        activities.get(0).moveFocusableActivityToTop("testDesktopModeDeliverToTop");
+
+        // Start activity and delivered new intent.
+        starter.getIntent().setComponent(activities.get(3).mActivityComponent);
+        doReturn(activities.get(3)).when(mRootWindowContainer).findTask(any(), any());
+        final int result = starter.setReason("testDesktopModeDeliverToTop").execute();
+
+        // Ensure result is delivering intent to top.
+        assertEquals(START_DELIVERED_TO_TOP, result);
+    }
+
+    /**
+     * This test ensures that if the intent is being delivered to a desktop mode unfocused task
+     * reports it is brought to front instead of delivering to top.
+     */
+    @Test
+    public void testDesktopModeTaskToFront() {
+        final ActivityStarter starter = prepareStarter(
+                FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | FLAG_ACTIVITY_SINGLE_TOP, false);
+        final List<ActivityRecord> activities = createActivitiesInDesktopMode();
+        final ActivityRecord desktopModeFocusActivity = activities.get(0);
+        final ActivityRecord desktopModeReusableActivity = activities.get(1);
+        final ActivityRecord desktopModeTopActivity = new ActivityBuilder(mAtm).setCreateTask(true)
+                .setParentTask(desktopModeReusableActivity.getRootTask()).build();
+        assertTrue(desktopModeTopActivity.inMultiWindowMode());
+
+        // Let first stack has focus.
+        desktopModeFocusActivity.moveFocusableActivityToTop("testDesktopModeTaskToFront");
+
+        // Start activity and delivered new intent.
+        starter.getIntent().setComponent(desktopModeReusableActivity.mActivityComponent);
+        doReturn(desktopModeReusableActivity).when(mRootWindowContainer).findTask(any(), any());
+        final int result = starter.setReason("testDesktopModeMoveToFront").execute();
+
+        // Ensure result is moving task to front.
+        assertEquals(START_TASK_TO_FRONT, result);
+    }
+
+    /** Returns 4 activities. */
+    private List<ActivityRecord> createActivitiesInDesktopMode() {
+        final TestDesktopOrganizer desktopOrganizer = new TestDesktopOrganizer(mAtm);
+        List<ActivityRecord> activityRecords = new ArrayList<>();
+
+        for (int i = 0; i < 4; i++) {
+            Rect bounds = new Rect(desktopOrganizer.getDefaultDesktopTaskBounds());
+            bounds.offset(20 * i, 20 * i);
+            desktopOrganizer.createTask(bounds);
+        }
+
+        for (int i = 0; i < 4; i++) {
+            activityRecords.add(new TaskBuilder(mSupervisor)
+                    .setParentTask(desktopOrganizer.mTasks.get(i))
+                    .setCreateActivity(true)
+                    .build()
+                    .getTopMostActivity());
+        }
+
+        for (int i = 0; i < 4; i++) {
+            activityRecords.get(i).setVisibleRequested(true);
+        }
+
+        for (int i = 0; i < 4; i++) {
+            assertEquals(desktopOrganizer.mTasks.get(i), activityRecords.get(i).getRootTask());
+        }
+
+        return activityRecords;
+    }
+
     @Test
     public void testMoveVisibleTaskToFront() {
         final ActivityRecord activity = new TaskBuilder(mSupervisor)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 114b9c3..c7c7913 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -20,6 +20,7 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -133,7 +134,9 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 
 /** Common base class for window manager unit test classes. */
 class WindowTestsBase extends SystemServiceTestsBase {
@@ -1892,6 +1895,55 @@
         }
     }
 
+    static class TestDesktopOrganizer extends WindowOrganizerTests.StubOrganizer {
+        final int mDesktopModeDefaultWidthDp = 840;
+        final int mDesktopModeDefaultHeightDp = 630;
+        final int mDesktopDensity = 284;
+
+        final ActivityTaskManagerService mService;
+        final TaskDisplayArea mDefaultTDA;
+        List<Task> mTasks;
+        final DisplayContent mDisplay;
+        Rect mStableBounds;
+
+        TestDesktopOrganizer(ActivityTaskManagerService service, DisplayContent display) {
+            mService = service;
+            mDefaultTDA = display.getDefaultTaskDisplayArea();
+            mDisplay = display;
+            mService.mTaskOrganizerController.registerTaskOrganizer(this);
+            mTasks = new ArrayList<>();
+            mStableBounds = display.getBounds();
+        }
+
+        TestDesktopOrganizer(ActivityTaskManagerService service) {
+            this(service, service.mTaskSupervisor.mRootWindowContainer.getDefaultDisplay());
+        }
+
+        public Task createTask(Rect bounds) {
+            Task task = mService.mTaskOrganizerController.createRootTask(
+                    mDisplay, WINDOWING_MODE_FREEFORM, null);
+            task.setBounds(bounds);
+            mTasks.add(task);
+            spyOn(task);
+            return task;
+        }
+
+        public Rect getDefaultDesktopTaskBounds() {
+            int width = (int) (mDesktopModeDefaultWidthDp * mDesktopDensity + 0.5f);
+            int height = (int) (mDesktopModeDefaultHeightDp * mDesktopDensity + 0.5f);
+            Rect outBounds = new Rect();
+
+            outBounds.set(0, 0, width, height);
+            // Center the task in stable bounds
+            outBounds.offset(
+                    mStableBounds.centerX() - outBounds.centerX(),
+                    mStableBounds.centerY() - outBounds.centerY()
+            );
+            return outBounds;
+        }
+
+    }
+
     static TestWindowToken createTestWindowToken(int type, DisplayContent dc) {
         return createTestWindowToken(type, dc, false /* persistOnEmpty */);
     }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9ec5f7a..67bb1f0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6888,6 +6888,7 @@
         }
     }
 
+    // TODO(b/316183370): replace all @code with @link in javadoc after feature is released
     /**
      * @return true if the current device is "voice capable".
      * <p>
@@ -6901,7 +6902,10 @@
      * PackageManager.FEATURE_TELEPHONY system feature, which is available
      * on any device with a telephony radio, even if the device is
      * data-only.
-     * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
+     * @deprecated Replaced by {@code #isDeviceVoiceCapable()}. Starting from Android 15, voice
+     * capability may also be overridden by carriers for a given subscription. For voice capable
+     * device (when {@code #isDeviceVoiceCapable} return {@code true}), caller should check for
+     * subscription-level voice capability as well. See {@code #isDeviceVoiceCapable} for details.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
     @Deprecated
@@ -6923,9 +6927,10 @@
      * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
      * radio, even if the device is data-only.
      * <p>
-     * To check if a subscription is "voice capable", call method
-     * {@link SubscriptionInfo#getServiceCapabilities()} and compare  the result with
-     * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+     * Starting from Android 15, voice capability may also be overridden by carrier for a given
+     * subscription on a voice capable device. To check if a subscription is "voice capable",
+     * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+     * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} is included.
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
@@ -6943,7 +6948,10 @@
      * <p>
      * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
      *       disabled when device doesn't support sms.
-     * @deprecated Replaced by {@link #isDeviceSmsCapable()}
+     * @deprecated Replaced by {@code #isDeviceSmsCapable()}. Starting from Android 15, SMS
+     * capability may also be overridden by carriers for a given subscription. For SMS capable
+     * device (when {@code #isDeviceSmsCapable} return {@code true}), caller should check for
+     * subscription-level SMS capability as well. See {@code #isDeviceSmsCapable} for details.
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public boolean isSmsCapable() {
@@ -6961,9 +6969,10 @@
      * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
      *       disabled when device doesn't support SMS.
      * <p>
-     * To check if a subscription is "SMS capable", call method
-     * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
-     * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+     * Starting from Android 15, SMS capability may also be overridden by carriers for a given
+     * subscription on an SMS capable device. To check if a subscription is "SMS capable",
+     * call method {@code SubscriptionInfo#getServiceCapabilities()} and check if
+     * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS} is included.
      *
      * @see SubscriptionInfo#getServiceCapabilities()
      */
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index b058631..60a9f0b 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -292,11 +292,13 @@
         setVirtualMousePointerDisplayIdAndVerify(10)
 
         localService.setPointerIconVisible(false, 10)
+        verify(native).setPointerIconVisibility(10, false)
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
         localService.setMousePointerAccelerationEnabled(false, 10)
         verify(native).setMousePointerAccelerationEnabled(eq(false))
 
         service.onDisplayRemoved(10)
+        verify(native).setPointerIconVisibility(10, true)
         verify(native).displayRemoved(eq(10))
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
         verify(native).setMousePointerAccelerationEnabled(true)
@@ -315,17 +317,20 @@
 
         localService.setPointerIconVisible(false, 10)
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL))
+        verify(native).setPointerIconVisibility(10, false)
         localService.setMousePointerAccelerationEnabled(false, 10)
         verify(native).setMousePointerAccelerationEnabled(eq(false))
 
         localService.setPointerIconVisible(true, 10)
         verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED))
+        verify(native).setPointerIconVisibility(10, true)
         localService.setMousePointerAccelerationEnabled(true, 10)
         verify(native).setMousePointerAccelerationEnabled(eq(true))
 
         // Verify that setting properties on a different display is not propagated until the
         // pointer is moved to that display.
         localService.setPointerIconVisible(false, 20)
+        verify(native).setPointerIconVisibility(20, false)
         localService.setMousePointerAccelerationEnabled(false, 20)
         verifyNoMoreInteractions(native)
 
@@ -341,6 +346,7 @@
         localService.setPointerIconVisible(false, 10)
         localService.setMousePointerAccelerationEnabled(false, 10)
 
+        verify(native).setPointerIconVisibility(10, false)
         verifyNoMoreInteractions(native)
 
         setVirtualMousePointerDisplayIdAndVerify(10)