diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index 9708d9a02edc882cc2af9894a6020731e290579d..06cc068e4d8001abcab211195215cc8e8107b1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -51,6 +51,7 @@ import com.android.systemui.util.time.SystemClock;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -145,6 +146,10 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
     protected void setListening(boolean listening) {
         mListening = listening;
         if (listening) {
+            // System UI could be restarted while ops are active, so fetch the currently active ops
+            // once System UI starts listening again.
+            fetchCurrentActiveOps();
+
             mAppOps.startWatchingActive(OPS, this);
             mAppOps.startWatchingNoted(OPS, this);
             mAudioManager.registerAudioRecordingCallback(mAudioRecordingCallback, mBGHandler);
@@ -177,6 +182,29 @@ public class AppOpsControllerImpl extends BroadcastReceiver implements AppOpsCon
         }
     }
 
+    private void fetchCurrentActiveOps() {
+        List<AppOpsManager.PackageOps> packageOps = mAppOps.getPackagesForOps(OPS);
+        for (AppOpsManager.PackageOps op : packageOps) {
+            for (AppOpsManager.OpEntry entry : op.getOps()) {
+                for (Map.Entry<String, AppOpsManager.AttributedOpEntry> attributedOpEntry :
+                        entry.getAttributedOpEntries().entrySet()) {
+                    if (attributedOpEntry.getValue().isRunning()) {
+                        onOpActiveChanged(
+                                entry.getOpStr(),
+                                op.getUid(),
+                                op.getPackageName(),
+                                /* attributionTag= */ attributedOpEntry.getKey(),
+                                /* active= */ true,
+                                // AppOpsManager doesn't have a way to fetch attribution flags or
+                                // chain ID given an op entry, so default them to none.
+                                AppOpsManager.ATTRIBUTION_FLAGS_NONE,
+                                AppOpsManager.ATTRIBUTION_CHAIN_ID_NONE);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Adds a callback that will get notifified when an AppOp of the type the controller tracks
      * changes
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 56ea703668d0de6aa26360383629ecf163c88327..68321f433c8126f37468b7fd26652a38c41b690d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -128,8 +128,9 @@ constructor(
     override fun onStatusEvent(event: StatusEvent) {
         Assert.isMainThread()
 
-        // Ignore any updates until the system is up and running
-        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+        // Ignore any updates until the system is up and running. However, for important events that
+        // request to be force visible (like privacy), ignore whether it's too early.
+        if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
             return
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
index 5fa83ef5d454e671a3f93041a6a4974395a3706a..6b5a548b0afe1c3001da155c5aca05e83a6ab83b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerLegacyImpl.kt
@@ -93,8 +93,9 @@ constructor(
     @SystemAnimationState override fun getAnimationState() = animationState
 
     override fun onStatusEvent(event: StatusEvent) {
-        // Ignore any updates until the system is up and running
-        if (isTooEarly() || !isImmersiveIndicatorEnabled()) {
+        // Ignore any updates until the system is up and running. However, for important events that
+        // request to be force visible (like privacy), ignore whether it's too early.
+        if ((isTooEarly() && !event.forceVisible) || !isImmersiveIndicatorEnabled()) {
             return
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index 61a651234e0c3c48ec38106789f560767584f781..e6c36c18342c61aa9f2b2f9b31eaadf4b81668c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -19,6 +19,8 @@ package com.android.systemui.appops;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.TestCase.assertFalse;
 
 import static org.junit.Assert.assertEquals;
@@ -66,6 +68,7 @@ import org.mockito.MockitoAnnotations;
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -157,6 +160,204 @@ public class AppOpsControllerTest extends SysuiTestCase {
         verify(mSensorPrivacyController, times(1)).removeCallback(mController);
     }
 
+    @Test
+    public void startListening_fetchesCurrentActive_none() {
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of());
+
+        mController.setListening(true);
+
+        assertThat(mController.getActiveAppOps()).isEmpty();
+    }
+
+    /** Regression test for b/294104969. */
+    @Test
+    public void startListening_fetchesCurrentActive_oneActive() {
+        AppOpsManager.PackageOps packageOps = createPackageOp(
+                "package.test",
+                /* packageUid= */ 2,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the op
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(1, list.size());
+        AppOpItem first = list.get(0);
+        assertThat(first.getPackageName()).isEqualTo("package.test");
+        assertThat(first.getUid()).isEqualTo(2);
+        assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+    }
+
+    @Test
+    public void startListening_fetchesCurrentActive_multiplePackages() {
+        AppOpsManager.PackageOps packageOps1 = createPackageOp(
+                "package.one",
+                /* packageUid= */ 1,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        AppOpsManager.PackageOps packageOps2 = createPackageOp(
+                "package.two",
+                /* packageUid= */ 2,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ false);
+        AppOpsManager.PackageOps packageOps3 = createPackageOp(
+                "package.three",
+                /* packageUid= */ 3,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the ops
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+
+        AppOpItem item0 = list.get(0);
+        assertThat(item0.getPackageName()).isEqualTo("package.one");
+        assertThat(item0.getUid()).isEqualTo(1);
+        assertThat(item0.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+
+        AppOpItem item1 = list.get(1);
+        assertThat(item1.getPackageName()).isEqualTo("package.three");
+        assertThat(item1.getUid()).isEqualTo(3);
+        assertThat(item1.getCode()).isEqualTo(AppOpsManager.OP_FINE_LOCATION);
+    }
+
+    @Test
+    public void startListening_fetchesCurrentActive_multipleEntries() {
+        AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+        when(packageOps.getUid()).thenReturn(1);
+        when(packageOps.getPackageName()).thenReturn("package.one");
+
+        // Entry 1
+        AppOpsManager.OpEntry entry1 = mock(AppOpsManager.OpEntry.class);
+        when(entry1.getOpStr()).thenReturn(AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE);
+        AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed1.isRunning()).thenReturn(true);
+        when(entry1.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed1));
+        // Entry 2
+        AppOpsManager.OpEntry entry2 = mock(AppOpsManager.OpEntry.class);
+        when(entry2.getOpStr()).thenReturn(AppOpsManager.OPSTR_CAMERA);
+        AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed2.isRunning()).thenReturn(true);
+        when(entry2.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed2));
+        // Entry 3
+        AppOpsManager.OpEntry entry3 = mock(AppOpsManager.OpEntry.class);
+        when(entry3.getOpStr()).thenReturn(AppOpsManager.OPSTR_FINE_LOCATION);
+        AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed3.isRunning()).thenReturn(false);
+        when(entry3.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed3));
+
+        when(packageOps.getOps()).thenReturn(List.of(entry1, entry2, entry3));
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the ops
+        List<AppOpItem> list = mController.getActiveAppOps();
+        assertEquals(2, list.size());
+
+        AppOpItem first = list.get(0);
+        assertThat(first.getPackageName()).isEqualTo("package.one");
+        assertThat(first.getUid()).isEqualTo(1);
+        assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_PHONE_CALL_MICROPHONE);
+
+        AppOpItem second = list.get(1);
+        assertThat(second.getPackageName()).isEqualTo("package.one");
+        assertThat(second.getUid()).isEqualTo(1);
+        assertThat(second.getCode()).isEqualTo(AppOpsManager.OP_CAMERA);
+    }
+
+    @Test
+    public void startListening_fetchesCurrentActive_multipleAttributes() {
+        AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+        when(packageOps.getUid()).thenReturn(1);
+        when(packageOps.getPackageName()).thenReturn("package.one");
+        AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+        when(entry.getOpStr()).thenReturn(AppOpsManager.OPSTR_RECORD_AUDIO);
+
+        AppOpsManager.AttributedOpEntry attributed1 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed1.isRunning()).thenReturn(false);
+        AppOpsManager.AttributedOpEntry attributed2 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed2.isRunning()).thenReturn(true);
+        AppOpsManager.AttributedOpEntry attributed3 = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed3.isRunning()).thenReturn(true);
+        when(entry.getAttributedOpEntries()).thenReturn(
+                Map.of("attr1", attributed1, "attr2", attributed2, "attr3", attributed3));
+
+        when(packageOps.getOps()).thenReturn(List.of(entry));
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps));
+
+        // WHEN we start listening
+        mController.setListening(true);
+
+        // THEN the active list has the ops
+        List<AppOpItem> list = mController.getActiveAppOps();
+        // Multiple attributes get merged into one entry in the active ops
+        assertEquals(1, list.size());
+
+        AppOpItem first = list.get(0);
+        assertThat(first.getPackageName()).isEqualTo("package.one");
+        assertThat(first.getUid()).isEqualTo(1);
+        assertThat(first.getCode()).isEqualTo(AppOpsManager.OP_RECORD_AUDIO);
+    }
+
+    /** Regression test for b/294104969. */
+    @Test
+    public void addCallback_existingCallbacksNotifiedOfCurrentActive() {
+        AppOpsManager.PackageOps packageOps1 = createPackageOp(
+                "package.one",
+                /* packageUid= */ 1,
+                AppOpsManager.OPSTR_FINE_LOCATION,
+                /* isRunning= */ true);
+        AppOpsManager.PackageOps packageOps2 = createPackageOp(
+                "package.two",
+                /* packageUid= */ 2,
+                AppOpsManager.OPSTR_RECORD_AUDIO,
+                /* isRunning= */ true);
+        AppOpsManager.PackageOps packageOps3 = createPackageOp(
+                "package.three",
+                /* packageUid= */ 3,
+                AppOpsManager.OPSTR_PHONE_CALL_MICROPHONE,
+                /* isRunning= */ true);
+        when(mAppOpsManager.getPackagesForOps(AppOpsControllerImpl.OPS))
+                .thenReturn(List.of(packageOps1, packageOps2, packageOps3));
+
+        // WHEN we start listening
+        mController.addCallback(
+                new int[]{AppOpsManager.OP_RECORD_AUDIO, AppOpsManager.OP_FINE_LOCATION},
+                mCallback);
+        mTestableLooper.processAllMessages();
+
+        // THEN the callback is notified of the current active ops it cares about
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_FINE_LOCATION,
+                /* uid= */ 1,
+                "package.one",
+                true);
+        verify(mCallback).onActiveStateChanged(
+                AppOpsManager.OP_RECORD_AUDIO,
+                /* uid= */ 2,
+                "package.two",
+                true);
+        verify(mCallback, never()).onActiveStateChanged(
+                AppOpsManager.OP_PHONE_CALL_MICROPHONE,
+                /* uid= */ 3,
+                "package.three",
+                true);
+    }
+
     @Test
     public void addCallback_includedCode() {
         mController.addCallback(
@@ -673,6 +874,22 @@ public class AppOpsControllerTest extends SysuiTestCase {
         assertEquals(AppOpsManager.OP_PHONE_CALL_CAMERA, list.get(cameraIdx).getCode());
     }
 
+    private AppOpsManager.PackageOps createPackageOp(
+            String packageName, int packageUid, String opStr, boolean isRunning) {
+        AppOpsManager.PackageOps packageOps = mock(AppOpsManager.PackageOps.class);
+        when(packageOps.getPackageName()).thenReturn(packageName);
+        when(packageOps.getUid()).thenReturn(packageUid);
+        AppOpsManager.OpEntry entry = mock(AppOpsManager.OpEntry.class);
+        when(entry.getOpStr()).thenReturn(opStr);
+        AppOpsManager.AttributedOpEntry attributed = mock(AppOpsManager.AttributedOpEntry.class);
+        when(attributed.isRunning()).thenReturn(isRunning);
+
+        when(packageOps.getOps()).thenReturn(Collections.singletonList(entry));
+        when(entry.getAttributedOpEntries()).thenReturn(Map.of("tag", attributed));
+
+        return packageOps;
+    }
+
     private class TestHandler extends AppOpsControllerImpl.H {
         TestHandler(Looper looper) {
             mController.super(looper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 914301f2e83022a63b21ccfb1a3a41f3827fc9a3..8a6dfe518cbf3593bf3664f06acf4344594bc898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -91,9 +91,6 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
                 fakeFeatureFlags
             )
 
-        // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
-        systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
-
         // StatusBarContentInsetProvider is mocked. Ensure that it returns some mocked values.
         whenever(statusBarContentInsetProvider.getStatusBarContentInsetsForCurrentRotation())
             .thenReturn(android.util.Pair(10, 10))
@@ -154,6 +151,21 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
         assertEquals(0f, batteryChip.view.alpha)
     }
 
+    /** Regression test for b/294104969. */
+    @Test
+    fun testPrivacyStatusEvent_beforeSystemUptime_stillDisplayed() = runTest {
+        initializeSystemStatusAnimationScheduler(testScope = this, advancePastMinUptime = false)
+
+        // WHEN the uptime hasn't quite passed the minimum required uptime...
+        systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME / 2)
+
+        // BUT the event is a privacy event
+        createAndScheduleFakePrivacyEvent()
+
+        // THEN the privacy event still happens
+        assertEquals(ANIMATION_QUEUED, systemStatusAnimationScheduler.getAnimationState())
+    }
+
     @Test
     fun testPrivacyStatusEvent_standardAnimationLifecycle() = runTest {
         // Instantiate class under test with TestScope from runTest
@@ -530,7 +542,10 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
         return batteryChip
     }
 
-    private fun initializeSystemStatusAnimationScheduler(testScope: TestScope) {
+    private fun initializeSystemStatusAnimationScheduler(
+        testScope: TestScope,
+        advancePastMinUptime: Boolean = true,
+    ) {
         systemStatusAnimationScheduler =
             SystemStatusAnimationSchedulerImpl(
                 systemEventCoordinator,
@@ -542,5 +557,10 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
             )
         // add a mock listener
         systemStatusAnimationScheduler.addCallback(listener)
+
+        if (advancePastMinUptime) {
+            // ensure that isTooEarly() check in SystemStatusAnimationScheduler does not return true
+            systemClock.advanceTime(Process.getStartUptimeMillis() + MIN_UPTIME)
+        }
     }
 }