diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml
new file mode 100644
index 0000000000000000000000000000000000000000..02f5f92ec5fede6601c724bc18fe9f725ff45f4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/hearing.xml
@@ -0,0 +1,24 @@
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48"
+    android:tint="?attr/colorControlNormal">
+  <path
+      android:fillColor="@android:color/white"
+      android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/>
+</vector>
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
index 35af44442ca92233ccbd9bf025400c232efc5e19..e3ed2b405fb00d2cc4768e5f46f4b39170cd92ce 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java
@@ -16,20 +16,34 @@
 
 package com.android.systemui.volume;
 
+import static android.app.PendingIntent.FLAG_IMMUTABLE;
+
 import android.annotation.StringRes;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.media.AudioManager;
-import android.os.CountDownTimer;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.WindowManager;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.util.NotificationChannels;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
 
 /**
  * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}:
@@ -53,34 +67,58 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
  * communication between the native audio framework that implements the dose computation and the
  * audio service.
  */
-public abstract class CsdWarningDialog extends SystemUIDialog
+public class CsdWarningDialog extends SystemUIDialog
         implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener {
 
     private static final String TAG = Util.logTag(CsdWarningDialog.class);
 
     private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds
     // time after which action is taken when the user hasn't ack'd or dismissed the dialog
-    private static final int NO_ACTION_TIMEOUT_MS = 5000;
+    public static final int NO_ACTION_TIMEOUT_MS = 5000;
 
     private final Context mContext;
     private final AudioManager mAudioManager;
     private final @AudioManager.CsdWarning int mCsdWarning;
     private final Object mTimerLock = new Object();
+
     /**
      * Timer to keep track of how long the user has before an action (here volume reduction) is
      * taken on their behalf.
      */
     @GuardedBy("mTimerLock")
-    private final CountDownTimer mNoUserActionTimer;
+    private Runnable mNoUserActionRunnable;
+    private Runnable mCancelScheduledNoUserActionRunnable = null;
+
+    private final DelayableExecutor mDelayableExecutor;
+    private NotificationManager mNotificationManager;
+    private Runnable mOnCleanup;
 
     private long mShowTime;
 
-    public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context,
-            AudioManager audioManager) {
+    /**
+     * To inject dependencies and allow for easier testing
+     */
+    @AssistedFactory
+    public interface Factory {
+        /**
+         * Create a dialog object
+         */
+        CsdWarningDialog create(int csdWarning, Runnable onCleanup);
+    }
+
+    @AssistedInject
+    public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context,
+            AudioManager audioManager, NotificationManager notificationManager,
+            @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) {
         super(context);
         mCsdWarning = csdWarning;
         mContext = context;
         mAudioManager = audioManager;
+        mNotificationManager = notificationManager;
+        mOnCleanup = onCleanup;
+
+        mDelayableExecutor = delayableExecutor;
+
         getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
         setShowForAllUsers(true);
         setMessage(mContext.getString(getStringForWarning(csdWarning)));
@@ -95,25 +133,24 @@ public abstract class CsdWarningDialog extends SystemUIDialog
                 Context.RECEIVER_EXPORTED_UNAUDITED);
 
         if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-            mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) {
-                @Override
-                public void onTick(long millisUntilFinished) { }
-
-                @Override
-                public void onFinish() {
-                    if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
-                        // unlike on the 5x dose repeat, level is only reduced to RS1
-                        // when the warning is not acknowledged quick enough
-                        mAudioManager.lowerVolumeToRs1();
-                    }
+            mNoUserActionRunnable = () -> {
+                if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) {
+                    // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning
+                    // is not acknowledged quickly enough
+                    mAudioManager.lowerVolumeToRs1();
+                    sendNotification();
                 }
             };
         } else {
-            mNoUserActionTimer = null;
+            mNoUserActionRunnable = null;
         }
     }
 
-    protected abstract void cleanUp();
+    private void cleanUp() {
+        if (mOnCleanup != null) {
+            mOnCleanup.run();
+        }
+    }
 
     // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than
     // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new
@@ -153,12 +190,9 @@ public abstract class CsdWarningDialog extends SystemUIDialog
         super.onStart();
         mShowTime = System.currentTimeMillis();
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                new Thread(() -> {
-                    synchronized (mTimerLock) {
-                        mNoUserActionTimer.start();
-                    }
-                }).start();
+            if (mNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed(
+                        mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS);
             }
         }
     }
@@ -166,8 +200,8 @@ public abstract class CsdWarningDialog extends SystemUIDialog
     @Override
     protected void onStop() {
         synchronized (mTimerLock) {
-            if (mNoUserActionTimer != null) {
-                mNoUserActionTimer.cancel();
+            if (mCancelScheduledNoUserActionRunnable != null) {
+                mCancelScheduledNoUserActionRunnable.run();
             }
         }
     }
@@ -212,4 +246,32 @@ public abstract class CsdWarningDialog extends SystemUIDialog
         Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception());
         return com.android.internal.R.string.csd_dose_reached_warning;
     }
+
+
+    /**
+     * In case user did not respond to the dialog, they still need to know volume was lowered.
+     */
+    private void sendNotification() {
+        Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS);
+        PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
+                FLAG_IMMUTABLE);
+
+        String text = mContext.getString(R.string.csd_system_lowered_text);
+        String title = mContext.getString(R.string.csd_lowered_title);
+
+        Notification.Builder builder =
+                new Notification.Builder(mContext, NotificationChannels.ALERTS)
+                        .setSmallIcon(R.drawable.hearing)
+                        .setContentTitle(title)
+                        .setContentText(text)
+                        .setContentIntent(pendingIntent)
+                        .setStyle(new Notification.BigTextStyle().bigText(text))
+                        .setVisibility(Notification.VISIBILITY_PUBLIC)
+                        .setLocalOnly(true)
+                        .setAutoCancel(true)
+                        .setCategory(Notification.CATEGORY_SYSTEM);
+
+        mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO,
+                builder.build());
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index 95cc12a48bb207891b1aa0e55cf1dd028b8dc60e..3c007f99a6546143b37100f935643d9793818b8e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -263,6 +263,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
     private final ConfigurationController mConfigurationController;
     private final MediaOutputDialogFactory mMediaOutputDialogFactory;
     private final VolumePanelFactory mVolumePanelFactory;
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory;
     private final ActivityStarter mActivityStarter;
 
     private boolean mShowing;
@@ -311,6 +312,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             Executor executor,
+            CsdWarningDialog.Factory csdWarningDialogFactory,
             DumpManager dumpManager) {
         mContext =
                 new ContextThemeWrapper(context, R.style.volume_dialog_theme);
@@ -322,6 +324,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
         mConfigurationController = configurationController;
         mMediaOutputDialogFactory = mediaOutputDialogFactory;
         mVolumePanelFactory = volumePanelFactory;
+        mCsdWarningDialogFactory = csdWarningDialogFactory;
         mActivityStarter = activityStarter;
         mShowActiveStreamOnly = showActiveStreamOnly();
         mHasSeenODICaptionsTooltip =
@@ -2084,21 +2087,21 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
         rescheduleTimeoutH();
     }
 
-    private void showCsdWarningH(int csdWarning, int durationMs) {
+    @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) {
         synchronized (mSafetyWarningLock) {
+
             if (mCsdDialog != null) {
                 return;
             }
-            mCsdDialog = new CsdWarningDialog(csdWarning,
-                    mContext, mController.getAudioManager()) {
-                @Override
-                protected void cleanUp() {
-                    synchronized (mSafetyWarningLock) {
-                        mCsdDialog = null;
-                    }
-                    recheckH(null);
+
+            final Runnable cleanUp = () -> {
+                synchronized (mSafetyWarningLock) {
+                    mCsdDialog = null;
                 }
+                recheckH(null);
             };
+
+            mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp);
             mCsdDialog.show();
         }
         recheckH(null);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
index 0ab6c690e1e14c177da34fd4cb7c2d85102866e9..14d3ca334073eb575da75f9d1ac0d0f2a1969d1c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java
@@ -30,17 +30,18 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.util.DeviceConfigProxy;
+import com.android.systemui.volume.CsdWarningDialog;
 import com.android.systemui.volume.VolumeComponent;
 import com.android.systemui.volume.VolumeDialogComponent;
 import com.android.systemui.volume.VolumeDialogImpl;
 import com.android.systemui.volume.VolumePanelFactory;
 
-import java.util.concurrent.Executor;
-
 import dagger.Binds;
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.concurrent.Executor;
+
 
 /** Dagger Module for code in the volume package. */
 @Module
@@ -63,6 +64,7 @@ public interface VolumeModule {
             InteractionJankMonitor interactionJankMonitor,
             DeviceConfigProxy deviceConfigProxy,
             @Main Executor executor,
+            CsdWarningDialog.Factory csdFactory,
             DumpManager dumpManager) {
         VolumeDialogImpl impl = new VolumeDialogImpl(
                 context,
@@ -76,6 +78,7 @@ public interface VolumeModule {
                 interactionJankMonitor,
                 deviceConfigProxy,
                 executor,
+                csdFactory,
                 dumpManager);
         impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
         impl.setAutomute(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..9cf3e443320d26f8efd07a88f8843539eb710f59
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.volume;
+
+import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X;
+import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.media.AudioManager;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class CsdWarningDialogTest extends SysuiTestCase {
+
+    private NotificationManager mNotificationManager;
+    private AudioManager mAudioManager;
+
+    @Before
+    public void setup() {
+        mNotificationManager = mock(NotificationManager.class);
+        getContext().addMockSystemService(NotificationManager.class, mNotificationManager);
+
+        mAudioManager = mock(AudioManager.class);
+        getContext().addMockSystemService(AudioManager.class, mAudioManager);
+    }
+
+    @Test
+    public void create1XCsdDialogAndWait_sendsNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        // instantiate directly instead of via factory; we don't want executor to be @Background
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+
+    @Test
+    public void create5XCsdDiSalogAndWait_willNotSendNotification() {
+        FakeExecutor executor =  new FakeExecutor(new FakeSystemClock());
+        CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext,
+                mAudioManager, mNotificationManager, executor, null);
+
+        dialog.show();
+        executor.advanceClockToLast();
+        executor.runAllReady();
+        dialog.dismiss();
+
+        verify(mNotificationManager, never()).notify(
+                eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class));
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
index d419095921b868c8b1b02cc3d21fd73971fff90c..eb2688894cb0a21e309855a52aa0561afb43b090 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java
@@ -102,6 +102,15 @@ public class VolumeDialogImplTest extends SysuiTestCase {
     InteractionJankMonitor mInteractionJankMonitor;
     @Mock
     private DumpManager mDumpManager;
+    @Mock CsdWarningDialog mCsdWarningDialog;
+
+    private final CsdWarningDialog.Factory mCsdWarningDialogFactory =
+            new CsdWarningDialog.Factory() {
+        @Override
+        public CsdWarningDialog create(int warningType, Runnable onCleanup) {
+            return mCsdWarningDialog;
+        }
+    };
 
     @Before
     public void setup() throws Exception {
@@ -124,6 +133,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
                 mInteractionJankMonitor,
                 mDeviceConfigProxy,
                 mExecutor,
+                mCsdWarningDialogFactory,
                 mDumpManager
             );
         mDialog.init(0, null);
@@ -352,6 +362,14 @@ public class VolumeDialogImplTest extends SysuiTestCase {
         mDialog.getDialogView().animate().cancel();
     }
 
+    @Test
+    public void showCsdWarning_dialogShown() {
+        mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X,
+                CsdWarningDialog.NO_ACTION_TIMEOUT_MS);
+
+        verify(mCsdWarningDialog).show();
+    }
+
 /*
     @Test
     public void testContentDescriptions() {
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 47027342974db6f1e692af74799d17af711f45d8..21d09792f1c7448d858fb2d6a834766766e3e65a 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -402,5 +402,7 @@ message SystemMessage {
     // Package: android
     NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006;
 
+    // Notify the user that audio was lowered based on Calculated Sound Dose (CSD)
+    NOTE_CSD_LOWER_AUDIO = 1007;
   }
 }